﻿using System;
using System.Collections.Generic;
using System.Text;

namespace GA_Scheduler
{
    public class AlgorithmStateEventArgs : EventArgs
    {
        public AlgorithmState State
        {
            get;
            private set;
        }

        public AlgorithmStateEventArgs(AlgorithmState st)
        {
            State = st;
        }

    }

    public class AlgorithmEventArgs : EventArgs
    {
        public Scheduler BestSchedule
        {
            get;
            private set;
        }

        public int Population
        {
            get;
            private set;
        }


        public AlgorithmEventArgs(Scheduler _chrom, int population)
        {
            BestSchedule = _chrom;
            Population = population;
        }
    }

    public class Algorithm
    {
        static Algorithm _instance = null;
        // Population of chromosomes
        List<Scheduler> _chromosomes = new List<Scheduler>();

        // Inidicates wheahter chromosome belongs to best chromosome group
        bool[] _bestFlags;

        // Indices of best chromosomes
        int[] _bestChromosomes;

        // Number of best chromosomes currently saved in best chromosome group
        int _currentBestSize;

        // Number of chromosomes which are replaced in each generation by offspring
        int _replaceByGeneration;



        // Prototype of chromosomes in population
        Scheduler _prototype;

        // Current generation
        int _currentGeneration;

        // State of execution of algorithm
        AlgorithmState _state;

        public event EventHandler<AlgorithmStateEventArgs> StateChanged;

        public event EventHandler<AlgorithmEventArgs> NewBestSchedule;

        public Algorithm(int numberOfChromosomes, int replaceByGeneration, int trackBest,
                     Scheduler prototype)
        {
            
            _replaceByGeneration = replaceByGeneration;
            _currentBestSize = 0;
            _prototype = prototype;
            _currentGeneration = 0;
            _state = AlgorithmState.AS_USER_STOPED;

            // there should be at least 2 chromosomes in population
            if (numberOfChromosomes < 2)
                numberOfChromosomes = 2;

            // and algorithm should track at least on of best chromosomes
            if (trackBest < 1)
                trackBest = 1;

            if (_replaceByGeneration < 1)
                _replaceByGeneration = 1;
            else if (_replaceByGeneration > numberOfChromosomes - trackBest)
                _replaceByGeneration = numberOfChromosomes - trackBest;

            for (int i = 0; i < numberOfChromosomes; i++)
            {
                _chromosomes.Add(null);
            }

            // reserve space for population
            //Array.Resize<Schedule>(ref _chromosomes, numberOfChromosomes);
            //_bestFlags.resize(numberOfChromosomes);
            _bestFlags = new bool[numberOfChromosomes];

            // reserve space for best chromosome group
            //_bestChromosomes.resize(trackBest);
            _bestChromosomes = new int[trackBest];

            // clear population
            for (int i = _chromosomes.Count - 1; i >= 0; --i)
            {
                _chromosomes[i] = null;
                _bestFlags[i] = false;
            }
        }

        public static int CrossPoints
        {
            get;
            set;
        }

        public static int MutationSize
        {
            get;
            set;
        }

        

        public static Algorithm GetInstance()
        {
            // global instance doesn't exist?
            if (_instance == null)
            {
                // make prototype of chromosomes
                //Schedule prototype = new Schedule(2, 2, 80, 3);
                Scheduler prototype = new Scheduler(6, 5, 80, 25);

                // make new global instance of algorithm using chromosome prototype
                _instance = new Algorithm(100, 20, 5, prototype);
                //_instance = new Algorithm(1000, 40, 50, prototype, new ScheduleObserver());
            }
            return _instance;
        }

      

        public void Start()
        {
            if (_prototype == null)
                return;

            // do not run already running algorithm
            if( _state == AlgorithmState.AS_RUNNING )
            	return;

            _state = AlgorithmState.AS_RUNNING;

         
            // clear best chromosome group from previous execution
            ClearBest();

            for (int i = 0; i < _chromosomes.Count; ++i)
            {
                // remove chromosome from previous execution
                //if( *it )
                //	delete *it;

                if (_chromosomes[i] != null)
                    _chromosomes[i] = null;

                // add new chromosome to population
                _chromosomes[i] = _prototype.MakeNewFromPrototype();
                AddToBest(i);
            }

            _currentGeneration = 0;
            OnNewBestChromosome(GetBestChromosome(), _currentGeneration);

            while (true)
            {
                // user has stopped execution?
                if( _state != AlgorithmState.AS_RUNNING )
                {
                    OnStateChanged(AlgorithmState.AS_USER_STOPED);
                    //lock.Unlock();
                    break;
                }

                Scheduler best = GetBestChromosome();

                // algorithm has reached criteria?
                if (best.GetFitness() >= 1)
                {
                    _state = AlgorithmState.AS_CRITERIA_STOPPED;
                    //lock.Unlock();
                    OnStateChanged(AlgorithmState.AS_CRITERIA_STOPPED);
                    break;
                }

                //lock.Unlock();

                // produce offepsing
                //vector<Schedule*> offspring;
                List<Scheduler> offspring = new List<Scheduler>();

                //Random rnd = new Random(Environment.TickCount);
                //offspring.resize( _replaceByGeneration );
                for (int i = 0; i < _replaceByGeneration; i++)
                    offspring.Add(null);


                for (int j = 0; j < _replaceByGeneration; j++)
                {
                    // selects parent randomly
                    int pos1 = rand() % _chromosomes.Count;
                    int pos2 = rand() % _chromosomes.Count;
                    /*if(IsInBest(pos1) || IsInBest(pos2))
                    {
                        j--;
                        continue;
                    }*/
                    Scheduler p1 = _chromosomes[pos1];
                    Scheduler p2 = _chromosomes[pos2];

                    /*p1.Crossover(ref p1, ref p2);
                    p1.Mutation();
                    p2.Mutation();

                    _chromosomes[pos1] = p1;
                    _chromosomes[pos2] = p2;

                    AddToBest(pos1);
                    AddToBest(pos2);*/
                    offspring[j] = p1.Crossover(p2);
                    if (!offspring[j].Equals(p1))
                        offspring[j].Mutation();
                    //else
                    //    offspring[j].Mutation();
                }

                // replace chromosomes of current operation with offspring
                for (int j = 0; j < _replaceByGeneration; j++)
                {
                    int ci;
                    do
                    {
                        // select chromosome for replacement randomly
                        ci = rand() % _chromosomes.Count;

                        // protect best chromosomes from replacement
                    } while (IsInBest(ci));

                    // replace chromosomes
                    //_chromosomes.RemoveAt(ci);

                    _chromosomes[ci] = offspring[j];

                    // try to add new chromosomes in best chromosome group
                    AddToBest(ci);
                }

                // algorithm has found new best chromosome
                if( best != GetBestChromosome())
                    OnNewBestChromosome(GetBestChromosome(), _currentGeneration);
                
                _currentGeneration++;


            }

            //if( _observer )
            // notify observer that execution of algorithm has changed it state
            //	_observer->EvolutionStateChanged( _state );
        }

        public void Stop()
        {
            _state = AlgorithmState.AS_USER_STOPED;
        }

        private int rand()
        {
            return Configuration.Instance.Rand.Next();
        }

        private Scheduler GetBestChromosome()
        {
            return _chromosomes[_bestChromosomes[0]];
        }

        private bool IsInBest(int chromosomeIndex)
        {
            return _bestFlags[chromosomeIndex];
        }

        private void ClearBest()
        {
            for (int i = _bestFlags.Length - 1; i >= 0; --i)
                _bestFlags[i] = false;

            _currentBestSize = 0;
        }

        private void AddToBest(int chromosomeIndex)
        {
            // don't add if new chromosome hasn't fitness big enough for best chromosome group
            // or it is already in the group?
            if ((_currentBestSize == _bestChromosomes.Length &&
                _chromosomes[_bestChromosomes[_currentBestSize - 1]].GetFitness() >=
                _chromosomes[chromosomeIndex].GetFitness()) || _bestFlags[chromosomeIndex])
                return;

            // find place for new chromosome
            int i = _currentBestSize;
            for (; i > 0; i--)
            {
                // group is not full?
                if (i < _bestChromosomes.Length)
                {
                    // position of new chromosomes is found?
                    if ((_chromosomes[_bestChromosomes[i - 1]] != null && _chromosomes[chromosomeIndex] != null) &&
                        _chromosomes[_bestChromosomes[i - 1]].GetFitness() > _chromosomes[chromosomeIndex].GetFitness())
                        break;

                    // move chromosomes to make room for new
                    _bestChromosomes[i] = _bestChromosomes[i - 1];
                }
                else
                    // group is full remove worst chromosomes in the group
                    _bestFlags[_bestChromosomes[i - 1]] = false;
            }

            // store chromosome in best chromosome group
            _bestChromosomes[i] = chromosomeIndex;
            _bestFlags[chromosomeIndex] = true;

            // increase current size if it has not reached the limit yet
            if (_currentBestSize < _bestChromosomes.Length)
                _currentBestSize++;
        }

        protected void OnStateChanged(AlgorithmState newState)
        {
            if (StateChanged != null)
                StateChanged(this, new AlgorithmStateEventArgs(newState));
        }

        protected void OnNewBestChromosome(Scheduler bchrom, int pop)
        {
            if (NewBestSchedule != null)
                NewBestSchedule(this, new AlgorithmEventArgs(bchrom, pop));
        }
    }
}
